﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Yaml.Presentation.Parsing.TokenScanners {
    abstract class TokenScanner {
        private Scanner scanner;
        private Mark start, end;

        public TokenScanner(Scanner scanner) {
            this.scanner = scanner;
            start = null;
            end = null;
        }

        public virtual Tokens.Token TryFetchToken() {
            if ( CheckForToken() ) {
                BeforeScan();
                ScanForToken();
                AfterScan();
                return CreateToken();
            }
            return null;
        }

        protected abstract bool CheckForToken();
        protected abstract void ScanForToken();
        protected abstract Tokens.Token CreateToken();

        protected Scanner Scanner { get { return scanner; } }
        protected Reader Reader { get { return scanner.Reader; } }
        protected Mark Start { get { return start; } }
        protected Mark End { get { return end; } }

        protected virtual void BeforeScan() {
            start = Scanner.GetMark();
        }

        protected virtual void AfterScan() {
            end = Scanner.GetMark();
        }

        protected void SaveSimpleKey() {
            bool required = Scanner.State.IsSimpleKeyRequired;
            int tokenNumber = Scanner.Tokens.NextTokenNumber;
            int flowDepth = Scanner.State.FlowDepth;
            Scanner.State.SimpleKeys.SaveSimpleKey(flowDepth, new SimpleKey(tokenNumber, required, Start));
            Scanner.State.SimpleKeys.IsSimpleKeyAllowed = true;
        }

        protected void RemoveSimpleKey(bool simpleKeyAllowed) {
            Scanner.State.SimpleKeys.RemoveSimpleKey(Scanner.State.FlowDepth);
            Scanner.State.SimpleKeys.IsSimpleKeyAllowed = simpleKeyAllowed;
        }

        /// <summary>
        /// Scan the "stream" looking for a valid tag handle.
        /// </summary>
        /// <remarks>
        /// The handle returned will be one of the following forms:
        /// ! (primary handle)
        /// !! (secondary handle)
        /// !handle! (named handle)
        /// </remarks>
        /// <returns>The handle that was found</returns>
        protected string ScanTagHandle() {
            return ScanTagHandle(true);
        }

        /// <summary>
        /// Scan the "stream" looking for a valid tag handle.
        /// </summary>
        /// <remarks>
        /// If strict is set to false it won't throw exception if handle does not end in !
        /// This is used by the TagTokenScanner where the handle may actaully be a primary handle followed by a suffix
        /// </remarks>
        protected string ScanTagHandle(bool strict) {
            StringBuilder handle = new StringBuilder();
            char ch = Reader.Peek();
            if (ch != '!') {
                throw new ScannerException("expected '!', but found '" + ch + "'");
            }
            handle.Append(ch);
            Scanner.Forward(1);
            ch = Reader.Peek();
            if (ch != ' ') {
                // NON_CONFORMING: Despite the spec this allows -_ in handles
                while (Scanner.IsAlphanumeric(ch)) {
                    handle.Append(ch);
                    Scanner.Forward(1);
                    ch = Reader.Peek();
                }
                if (strict && ch != '!') {
                    throw new ScannerException("expected '!', but found '" + ch + "'");
                }
                if (ch == '!') {
                    handle.Append(ch);
                }
                Scanner.Forward(1);
            }
            return handle.ToString();
        }

        protected string ScanTagContent() {
            if (Reader.Peek() == '!') {
                return ScanLocalTagContent();
            } else {
                return ScanUriTagContent();
            }
        }

        private string ScanLocalTagContent() {
            StringBuilder prefix = new StringBuilder();
            char ch = Reader.Peek();
            if (ch != '!') {
                throw new ScannerException("expected '!', but found " + ch);
            }
            prefix.Append(ch);
            Scanner.Forward(1);
            ch = Reader.Peek();
            // Despite the spec this allows -_ in handles
            while (Scanner.IsAlphanumeric(ch)) {
                prefix.Append(ch);
                Scanner.Forward(1);
                ch = Reader.Peek();
            }
            // NON_COMFORMING - the spec actually does allow "!" as a local prefix but this seems wrong as would it not be the same as the primary handle?
            if ( prefix.Length <= 1 ) {
                throw new ScannerException("While scanning tag content at " + Start.ToString() + ", invalid, zero length, local tag was found at " + Scanner.GetMark());
            }
            return prefix.ToString();
        }

        protected string ScanUriTagContent() {
            StringBuilder uriString = new StringBuilder();
            while (Scanner.IsUriChar(Reader.Peek())) {
                uriString.Append(Reader.Peek());
                Scanner.Forward(1);
            }
            try {
                Uri uri = new Uri(uriString.ToString());
                return uri.ToString();
            } catch (UriFormatException x) {
                throw new ScannerException("expected URI but found " + uriString.ToString(), x);
            }
        }
    }
}
